package com.lemon.core.mybatis.util;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggerFactory;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lottery
 * @ClassName MybatisPlusUtils
 * @desc
 * @Date 2018/7/24 12:35
 * @Version 1.0
 **/
public class MybatisPlusUtils {

    private static final Logger LOGGER = LogManager.getLogger(MybatisPlusUtils.class);

    /**
     *
     */
    private static PropertiesLoader propertiesLoader;

    /* -----必填配置参数-----start */

    /**
     * 数据库类型
     */
    private static DbType dbType;

    /**
     * ip:port/实例名
     */
    private static String url;

    /**
     * 用户名
     */
    private static String userName;

    /**
     * 密码
     */
    private static String password;

    /**
     * 项目根路径
     */
    private static String projectPath;

    /* -----必填配置参数-----end */


    /* -----可选配置参数-----start */

    /**
     * groupId
     */
    private static String groupId;

    /**
     * artifactId
     */
    private static String artifactId;

    /**
     * 二级package
     */
    private static String secondPackage;

    /**
     * 去除数据库表前缀
     */
    private static String[] tabPrefixs = new String[] {};

    /**
     * 生成哪些表
     * includeTabs和excludeTabs只需要配置一个
     */
    private static String[] includeTabs = new String[] {};

    /**
     * 去除哪些表
     * includeTabs和excludeTabs只需要配置一个
     */
    private static String[] excludeTabs = new String[] {};

    /**
     * 生成实体类是否使用lombok
     */
    private static boolean entityLombokModel = false;

    /**
     * 是否覆盖已生成的文件
     */
    private static boolean fileOverride = false;

    /* -----可选配置参数-----end */

    /**
     * myBatisPlusUtils 单例
     */
    private static MybatisPlusUtils myBatisPlusUtils = null;

    private MybatisPlusUtils() {

    }

    /**
     * 获取单例<br/>
     * 使用当前context中已有的配置去注入<br/>
     * 如果没有则抛异常
     * @return
     */
    public static MybatisPlusUtils getInstance() {
        if (null == myBatisPlusUtils) {
            synchronized (MybatisPlusUtils.class) {
                if (null == myBatisPlusUtils) {
                    if (initDefaultProperties()) {
                        myBatisPlusUtils = new MybatisPlusUtils();
                    }
                }
            }
        }
        return myBatisPlusUtils;
    }

    /**
     * 获取单例
     * @param dbType    数据库类型，如DbType.MYSQL
     * @param url       连接信息(ip:port/实例名)，如：101.132.134.51:3306/lottery
     * @param userName  用户名
     * @param password  密码
     * @return
     */
    public static MybatisPlusUtils getInstance(DbType dbType, String url, String userName, String password) {
        if (null == myBatisPlusUtils) {
            synchronized (MybatisPlusUtils.class) {
                if (null == myBatisPlusUtils) {
                    if (initRequiredParams(dbType, url, userName, password)) {
                        myBatisPlusUtils = new MybatisPlusUtils();
                    }
                }
            }
        }
        return myBatisPlusUtils;
    }

    /**
     * 可选参数: groupId
     * @param groupId mavne groupId
     * @return
     */
    public MybatisPlusUtils groupId(String groupId) {
        MybatisPlusUtils.groupId = groupId;
        return myBatisPlusUtils;
    }

    /**
     * 可选参数: artifactId
     * @param artifactId maven artifactId
     * @return
     */
    public MybatisPlusUtils artifactId(String artifactId) {
        MybatisPlusUtils.artifactId = artifactId;
        return myBatisPlusUtils;
    }

    /**
     * 可选参数: secondPackage
     * @param secondPackage 二级package
     * @return
     */
    public MybatisPlusUtils secondPackage(String secondPackage) {
        MybatisPlusUtils.secondPackage = secondPackage;
        return myBatisPlusUtils;
    }

    /**
     * 可选参数: tabPrefixs
     * @param tabPrefixs 需要去除的表名前缀
     * @return
     */
    public MybatisPlusUtils tabPrefixs(String[] tabPrefixs) {
        MybatisPlusUtils.tabPrefixs = tabPrefixs;
        return myBatisPlusUtils;
    }

    /**
     * 可选参数: includeTabs
     * @param includeTabs 需要生成的表名数组
     * @return
     */
    public MybatisPlusUtils includeTabs(String[] includeTabs) {
        MybatisPlusUtils.includeTabs = includeTabs;
        return myBatisPlusUtils;
    }

    /**
     * 可选参数: excludeTabs
     * @param excludeTabs 不需要生成的表名数组
     * @return
     */
    public MybatisPlusUtils excludeTabs(String[] excludeTabs) {
        MybatisPlusUtils.excludeTabs = excludeTabs;
        return myBatisPlusUtils;
    }

    /**
     * 可选参数：entityLombokModel
     * @param entityLombokModel 实体类是否试用lombok
     * @return
     */
    public MybatisPlusUtils entityLombokModel(boolean entityLombokModel) {
        MybatisPlusUtils.entityLombokModel = entityLombokModel;
        return myBatisPlusUtils;
    }

    public MybatisPlusUtils fileOverride(boolean fileOverride) {
        MybatisPlusUtils.fileOverride = fileOverride;
        return myBatisPlusUtils;
    }


    /**
     * 生成多模块代码<br/>
     * api:     实体<br/>
     * dao:     mapper的xml和java<br/>
     * service: 接口和实现类<br/>
     * web:     视图层<br/>
     * 备注: 如果api、dao、service、web在多个工程下,
     * 请传入module_name_api、module_name_dao、 module_name_service、module_name_web<br/>
     * @param modules 模块名
     */
    public void generateMultiModule(String... modules) {

        for (String module : modules) {
            generateModuleCode(module);
        }
    }

    /**
     * 生成单个模块代码
     * @param module 模块名
     */
    private void generateModuleCode(String module) {
        // 获取项目根路径
        projectPath = getProjectPath(module);

        if (!initOptionalParams()) {
            return;
        }

        // 代码生成器
        AutoGenerator autoGenerator = new AutoGenerator()
                .setGlobalConfig(globalConfig(projectPath))
                .setDataSource(dataSourceConfig(dbType, url, userName, password))
                .setStrategy(strategyConfig(tabPrefixs, includeTabs, excludeTabs))
                .setPackageInfo(packageConfig(groupId, artifactId))
                .setCfg(injectionConfig(module))
                .setTemplate(templateConfig(module));

        // 执行生成代码
        autoGenerator.execute();

        LOGGER.info(module + " 代码生成成功！！！");
    }

    /**
     * 全局配置
     * @param projectPath   项目根路径
     * @return
     */
    private GlobalConfig globalConfig(String projectPath) {
        // 全局配置
        return new GlobalConfig()
                // 输出目录
                .setOutputDir(projectPath + "/src/main/java")
                // 是否覆盖文件
                .setFileOverride(fileOverride)
                // 开启 activeRecord 模式
                .setActiveRecord(true)
                // XML 二级缓存
                .setEnableCache(false)
                // XML ResultMap
                .setBaseResultMap(true)
                // XML columList
                .setBaseColumnList(true)
                // 是否生成 kotlin 代码
                //.setKotlin(true)
                // 坐着信息
                .setAuthor("柠檬的柠丶")
                // 完成后自动打开文件夹
                .setOpen(false)
                // 自定义文件命名，注意 %s 会自动填充表实体属性！
                //.setEntityName("%sEntity");
                //.setMapperName("%sDao")
                //.setXmlName("%sDao")
                //.setServiceName("MP%sService")
                //.setServiceImplName("%sServiceDiy")
                //.setControllerName("%sAction")
                ;
    }

    /**
     * 数据源配置
     * @param dbType    数据库类型
     * @param url       数据库连接
     * @param userName  用户名
     * @param password  密码
     * @return
     */
    private DataSourceConfig dataSourceConfig(DbType dbType, String url, String userName, String password) {
        // 数据库驱动类
        String driverName = "";
        switch (dbType) {
            case MYSQL:
                driverName = "com.mysql.jdbc.Driver";
                break;
            case ORACLE:
                driverName = "oracle.jdbc.driver.OracleDriver";
                break;
            case POSTGRE_SQL:
                driverName = "";
                break;
            default:
                break;
        }

        // 数据源配置
        return new DataSourceConfig()
                // 数据库类型
                .setDbType(dbType)
                .setTypeConvert(new MySqlTypeConvert())
                .setDriverName(driverName)
                .setUrl(url)
                .setUsername(userName)
                .setPassword(password)
                ;
    }

    /**
     * 实体类生成策略
     * @param tabPrefixs    数据库表生成实体类去除的前缀名
     * @param includeTabs   生成哪些表
     * @param excludeTabs   去除哪些表
     * @return
     */
    private StrategyConfig strategyConfig(String[] tabPrefixs, String[] includeTabs, String[] excludeTabs) {
        return new StrategyConfig()
                //.setCapitalMode(true)// 全局大写命名
                //.setDbColumnUnderline(true)//全局下划线命名
                // 此处可以修改为您的表前缀
                .setTablePrefix(tabPrefixs)
                // 表名生成策略：驼峰
                .setNaming(NamingStrategy.underline_to_camel)
                // 需要生成的表
                .setInclude(includeTabs)
                // 排除生成的表
                .setExclude(excludeTabs)
                // 自定义实体父类
                // .setSuperEntityClass("com.baomidou.demo.TestEntity")
                // 自定义实体，公共字段
                //.setSuperEntityColumns(new String[]{"test_id"})
                //.setTableFillList(tableFillList)
                // 自定义 mapper 父类
                .setSuperMapperClass("com.lottery.core.db.mapper.BaseMapper")
                // 自定义 service 父类
                .setSuperServiceClass("com.lottery.core.db.service.IBaseService")
                // 自定义 service 实现类父类
                .setSuperServiceImplClass("com.lottery.core.db.service.impl.BaseServiceImpl")
                // 自定义 controller 父类
                //.setSuperControllerClass("com.baomidou.demo.TestController")
                // 【实体】是否生成字段常量（默认 false）
                // public static final String ID = "test_id";
                .setEntityColumnConstant(true)
                //【实体】是否为构建者模型（默认 false）
                //public User setName(String name) {this.name = name; return this;}
                //.setEntityBuilderModel(true)
                //【实体】是否为lombok模型（默认 false）<a href="https://projectlombok.org/">document</a>
                .setEntityLombokModel(entityLombokModel)
                //Boolean类型字段是否移除is前缀处理
                //.setEntityBooleanColumnRemoveIsPrefix(true)
                //.setRestControllerStyle(true)
                //.setControllerMappingHyphenStyle(true)
                ;
    }

    /**
     * 包路径配置
     * @param groupId       groupId
     * @param artifactId    artifactId
     * @return
     */
    private PackageConfig packageConfig(String groupId, String artifactId) {
        return new PackageConfig()
                // 自定义包路径
                .setParent(groupId)
                .setModuleName(artifactId)
                // 这里是控制器包名
                .setController("controller")
                .setService("service")
                .setServiceImpl("service.impl")
                .setMapper("mapper" + (StringUtils.isBlank(secondPackage) ? "" : "." + secondPackage))
                // 实体类报名
                .setEntity("entity")
                ;
    }

    /**
     * 注入自定义配置，可以在 VM 中使用 cfg.abc 设置的值
     * @param module 模块名
     * @return
     */
    private InjectionConfig injectionConfig(String module) {
        return new InjectionConfig() {
            @Override
            public void initMap() {
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("abc", this.getConfig().getGlobalConfig().getAuthor());
                this.setMap(map);
            }
        }.setFileOutConfigList(
                module.contains("dao") ?
                        Collections.<FileOutConfig>singletonList(
                                new FileOutConfig("/templates/lemon_mapper.xml.vm") {
                                    // 自定义Mapper文件输出目录
                                    @Override
                                    public String outputFile(TableInfo tableInfo) {
                                        return projectPath + "/src/main/resources/mapper/"
                                                + (StringUtils.isBlank(secondPackage) ? "" : secondPackage + "/")
                                                + tableInfo.getEntityName() + "Mapper.xml";
                                    }
                                }
                        )
                        : new ArrayList<FileOutConfig>()
        );
    }

    /**
     * 代码生成模板配置
     * @param module 模块名
     * @return
     */
    private TemplateConfig templateConfig(String module) {
        return new TemplateConfig()
                .setEntity(module.contains("api") ? "/templates/lemon_entity.java.vm" : null)
                // 关闭默认xml生成，使用自定义生成目录
                .setXml(null)
                .setMapper(module.contains("dao") ? "/templates/lemon_mapper.java.vm" : null)
                .setService(module.contains("service") ? "/templates/lemon_service.java.vm" : null)
                .setServiceImpl(module.contains("service") ? "/templates/lemon_serviceImpl.java.vm" : null)
                .setController(module.contains("controller") ? "/templates/lemon_controller.java.vm" : null)
                ;
    }

    /**
     * 获取项目跟路径
     * @param module 模块名
     * @return
     */
    private String getProjectPath(String module) {
        // 获取项目根路径
        String projectPath = "";
        try {
            File file = new File(ResourceUtils.getURL("classpath:").getPath());
            if (null != file) {
                projectPath = file.getParentFile().getParent();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("获取项目projectPath路径出错。");
        }

        if (StringUtils.isNotBlank(projectPath) && (module.contains("-") || module.contains("_"))) {
            projectPath += File.separator + module;
        }

        return projectPath;
    }

    /**
     * 检查myBatisPlusUtils是否初始化成功
     * @return
     */
    private boolean isInit() {
        if (null == myBatisPlusUtils) {
            throw new RuntimeException("请先初始化MyBatisPlusUtils实例.");
        }

        return true;
    }

    /**
     * 当没有传入url、dbType，默认从当前context取配置
     * @return
     */
    private static boolean initDefaultProperties() {
        propertiesLoader = new PropertiesLoader("application.properties");
        String activeProfile = propertiesLoader.getString("spring.profiles.active");
        propertiesLoader = new PropertiesLoader("application-" + activeProfile + ".properties");

        String url = propertiesLoader.getString("lemon.db.datasource.url");
        if (StringUtils.isBlank(url)) {
            throw new RuntimeException("配置文件未配置 lottery.db.datasource.url");
        }
        // 解析url获取 ip:port/dbName、dbType
        parseUrl(url);

        String userName = propertiesLoader.getString("lemon.db.datasource.username");
        if (StringUtils.isBlank(userName)) {
            throw new RuntimeException("配置文件未配置 lottery.db.datasource.username");
        }
        MybatisPlusUtils.userName = userName;


        String password = propertiesLoader.getString("lemon.db.datasource.password");
        if (StringUtils.isBlank(password)) {
            throw new RuntimeException("配置文件未配置 lottery.db.datasource.password");
        }
        MybatisPlusUtils.password = password;

        return true;
    }

    /**
     * 解析url获取url和dbType
     * @param url
     */
    private static void parseUrl(String url) {
        // 解析url获取 ip:port/dbName、dbType
        if (url.contains(DbType.MYSQL.getDb())) {
            MybatisPlusUtils.dbType = DbType.MYSQL;
            MybatisPlusUtils.url = url;
        } else if (url.contains(DbType.ORACLE.getDb())) {
            MybatisPlusUtils.dbType = DbType.ORACLE;
            MybatisPlusUtils.url = url;
        }
    }

    /**
     * 校验必选参数并初始化参数
     * @param dbType    数据库类型，如DbType.MYSQL
     * @param url       连接信息(ip:port/实例名)，如：101.132.134.51:3306/lottery
     * @param userName  用户名
     * @param password  密码
     * @return
     */
    private static boolean initRequiredParams(DbType dbType, String url, String userName, String password) {
        // 参数校验
        if (null == dbType) {
            throw new RuntimeException("数据库类型不可为空, 初始化MybatisPlusUtils实例失败.");
        }

        if (StringUtils.isBlank(url)) {
            throw new RuntimeException("连接信息不可为空, 初始化MybatisPlusUtils实例失败.");
        }

        if (StringUtils.isBlank(userName)) {
            throw new RuntimeException("用户名不可为空, 初始化MybatisPlusUtils实例失败.");
        }

        if (StringUtils.isBlank(password)) {
            throw new RuntimeException("密码不可为空, 初始化MybatisPlusUtils实例失败.");
        }

        // 根据数据库类型拼接数据库连接url
        StringBuffer wholeUrl = new StringBuffer();
        switch (dbType) {
            case MYSQL:
                wholeUrl.append("jdbc:mysql://").append(url).append("?characterEncoding=utf8");
                break;
            default:
                break;
        }

        if (StringUtils.isBlank(wholeUrl.toString())) {
            throw new RuntimeException("暂时不支持当前数据库, 初始化MybatisPlusUtils实例失败.");
        }

        // 初始化参数
        MybatisPlusUtils.dbType = dbType;
        MybatisPlusUtils.url = wholeUrl.toString();
        MybatisPlusUtils.userName = userName;
        MybatisPlusUtils.password = password;

        return true;
    }

    /**
     * 可选参数初始化
     */
    private static boolean initOptionalParams() {

        if (StringUtils.isBlank(groupId) || StringUtils.isBlank(artifactId)) {
            Model model = getMavenModel();

            if (null == model) {
                return false;
            }

            if (StringUtils.isBlank(groupId)) {
                groupId = model.getGroupId();
            }

            if (StringUtils.isBlank(artifactId)) {
                artifactId = model.getArtifactId().replaceAll("-", "").replaceAll("_", "");
            }
        }

        if (includeTabs.length != 0 && excludeTabs.length != 0) {
            throw new RuntimeException("includeTabs和excludeTabs只可以配置一个.");
        }

        return true;
    }

    /**
     * 获取maven配置
     * @return
     */
    private static Model getMavenModel() {
        MavenXpp3Reader reader = new MavenXpp3Reader();
        // pom文件路径
        String pomPath = projectPath + File.separator + "pom.xml";
        Model model = null;

        try {
            model = reader.read(new FileReader(pomPath));
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("读取pom文件异常，异常信息：" + e.getMessage());
        } catch (XmlPullParserException e) {
            e.printStackTrace();
            throw new RuntimeException("解析pom文件异常，异常信息：" + e.getMessage());
        }

        return model;
    }
}